//
//  GenericGLView.m
//  glf
//

#import "glf/iphone/GenericGLView.h"

#include <glf/debug/console.h>
#include <algorithm>

@interface GenericGLView ()

- (BOOL) createFrameBuffer;
- (void) destroyFrameBuffer;
- (void) initGL;

@end

@implementation GenericGLView

@synthesize context;
@synthesize screen;

@synthesize activateMSAA;
@synthesize desiredMSAA;

@synthesize requestedScreenBits;
@synthesize requestedDepthBits;
@synthesize requestedStencilBits;

@dynamic creationSettings;

@synthesize viewFramebuffer;

// You must implement this method
+ (Class) layerClass
{
    return [CAEAGLLayer class];
}

// Make sure the variables in the class have decent values BEFORE init is called
+ (id) alloc
{
    GenericGLView* newid = [super alloc];

    // Support for MSAA (1x, 2x, 4x)
    newid->activateMSAA = false;
    newid->desiredMSAA = 0;
    
    // Set default screen properties
    newid->requestedScreenBits = 32;
    newid->requestedDepthBits = 24;
    newid->requestedStencilBits = 8;
		
	newid->creationSettings = nil;
	newid->context = nil;
	newid->screen = nil;

    return newid;
}

-(glf::CreationSettings *)creationSettings
{
	return creationSettings;
}

-(void)setCreationSettings:(glf::CreationSettings *)inCreationSettings
{
	creationSettings = inCreationSettings;
	//init MSAA and screen properties according to CreationSettings
	if (creationSettings->antiAliasSetting != glf::AA_NONE) {
		activateMSAA = true;
		desiredMSAA = 
				creationSettings->antiAliasSetting == glf::AA_2X ? 2 : 
				creationSettings->antiAliasSetting == glf::AA_4X || creationSettings->antiAliasSetting == glf::AA_4XR ? 4 : 
				4;
	}
	else {
		activateMSAA = false;
		desiredMSAA = 0;
	}
	requestedDepthBits = creationSettings->zBufferSize;
	requestedStencilBits = creationSettings->useStencilbuffer ? 8 : 0;
	// For internal format compatibility, the depth buffer must be 24 bits wide 
	// when a stencil buffer is used (8 bits wide mandatory). This results in a 
	// packed depth/stencil buffer of 32 bits wide.
	// assert(requestedStencilBits == 8 implies requestedDepthBits == 24)
	GLF_ASSERT(requestedStencilBits != 8 || requestedDepthBits == 24);
}

- (id)initWithFrame:(CGRect)frame {
	GLF_ASSERT(creationSettings);
	GLF_ASSERT(screen);
	GLF_ASSERT(context);
    if (self = [super initWithFrame:frame]) 
	{
        // Init scale
		if([screen respondsToSelector:@selector(scale)]) 
		{
			if([self respondsToSelector:@selector(contentScaleFactor)]) {
				self.contentScaleFactor = [screen scale];
			}
		}		
		// Init layer properties
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
        eaglLayer.opaque = TRUE;        
		eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
			[NSNumber numberWithBool:FALSE], 
			kEAGLDrawablePropertyRetainedBacking, 
			requestedScreenBits > 16 ? kEAGLColorFormatRGBA8 : kEAGLColorFormatRGB565, 
			kEAGLDrawablePropertyColorFormat, 
			nil];
		
		viewRenderbuffer = viewFramebuffer = depthRenderbuffer = stencilRenderbuffer = 0;
		backingWidth = backingHeight = 0;	
#ifdef GL_APPLE_framebuffer_multisample
        resolveFramebuffer = resolveColorbuffer = 0;
#endif
		Stencil1Supported = Stencil4Supported = Stencil8Supported = false;
		PackedDepth24Stencil8Supported = false;
		Depth24Supported = Depth32Supported = false;			
		
		[self initGL];
		[self createFrameBuffer];
    }
    return self;
}

- (BOOL) createFrameBuffer
{
	GLF_ASSERT(viewFramebuffer == 0);
	
#ifdef GL_APPLE_framebuffer_multisample
    // Determine how many MSAS samples to use
    // Make sure we don't ask too much antialiasing for the HW
    GLint MSAASamples = 0;
    glGetIntegerv(GL_MAX_SAMPLES_APPLE, &MSAASamples);
    MSAASamples = std::min(desiredMSAA, MSAASamples);
    if(activateMSAA && MSAASamples > 0)
    {
        // Create the Resolve Framebuffer
        glGenFramebuffersOES(1, &resolveFramebuffer);
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, resolveFramebuffer);
        
        // Create the renderbuffer that is attached to CoreAnimation, and query the dimensions
        glGenRenderbuffersOES(1, &resolveColorbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, resolveColorbuffer);
        [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable: (CAEAGLLayer*)self.layer];
        glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
        glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, resolveColorbuffer);
        
        // Create the MSAA framebuffer (offscreen)
        glGenFramebuffersOES(1, &viewFramebuffer);
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
        
        // Create the offscreen MSAA color buffer.
        // After rendering, the contents of this will be blitted into resolveColorbuffer
        glGenRenderbuffersOES(1, &viewRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
        glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, MSAASamples, requestedScreenBits > 16 ? GL_RGBA8_OES : GL_RGB565, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
        
        // Create the MSAA depth and/or stencil buffer (if desired and HW supported)
#if GLF_USE_DEPTH_BUFFER
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
#	ifdef GL_OES_packed_depth_stencil
        BOOL usingPackedDepthStencil = false;
        if(PackedDepth24Stencil8Supported && requestedDepthBits == 24 && requestedStencilBits == 8)
        {
            glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, MSAASamples, GL_DEPTH24_STENCIL8_OES, backingWidth, backingHeight);
            usingPackedDepthStencil = true;
        }
        else
#	endif
#	ifdef GL_OES_depth24
        if (Depth24Supported && requestedDepthBits == 24)
        {
            glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, MSAASamples, GL_DEPTH_COMPONENT24_OES, backingWidth, backingHeight);
        }
        else
#	endif
        {
            glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, MSAASamples, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        }
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
#endif
        
#if GLF_HAS_STENCIL_BUFFER
#    if defined(GL_OES_packed_depth_stencil) && GLF_USE_DEPTH_BUFFER
        if(usingPackedDepthStencil)
        {
            glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
        }
        else
#    endif
#    if defined(GL_OES_stencil8) || defined(GL_ES_VERSION_2_0)
        if(Stencil8Supported && requestedStencilBits >= 8)
        {
            glGenRenderbuffersOES(1, &stencilRenderbuffer);
            glBindRenderbufferOES(GL_RENDERBUFFER_OES, stencilRenderbuffer);
            glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, MSAASamples, GL_STENCIL_INDEX8_OES, backingWidth, backingHeight);
            glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, stencilRenderbuffer);
        }
#    endif
#endif
        
        // Validate the MSAA framebuffer
        if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES)
        {
            glf::Console::Print("Failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
            [self destroyFrameBuffer];
            return NO;
        }
        
        // Validate the resolve framebuffer
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, resolveFramebuffer);
        if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES)
        {
            glf::Console::Print("Failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
            [self destroyFrameBuffer];
            return NO;
        }
        
        // Restore the view framebuffer before creating the Glitch device as it will query it
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    }
    else 
#endif
    {
        glGenFramebuffersOES(1, &viewFramebuffer);
        glGenRenderbuffersOES(1, &viewRenderbuffer);
    
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
        [context renderbufferStorage:GL_RENDERBUFFER_OES 
                                  fromDrawable:(CAEAGLLayer*)self.layer];
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
    
        glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
        glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
    
#if GLF_USE_DEPTH_BUFFER
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
#    ifdef GL_OES_packed_depth_stencil
        BOOL usingPackedDepthStencil = false;
        if(PackedDepth24Stencil8Supported && requestedDepthBits == 24 && requestedStencilBits == 8)
        {
            glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH24_STENCIL8_OES, backingWidth, backingHeight);
            usingPackedDepthStencil = true;
        }
        else
#    endif
#	ifdef GL_OES_depth24
        if (Depth24Supported && requestedDepthBits == 24)
        {
            glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT24_OES, backingWidth, backingHeight);
        }
        else
#	endif
        {
            glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        }
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
#endif

#if GLF_HAS_STENCIL_BUFFER
#    if defined(GL_OES_packed_depth_stencil) && GLF_USE_DEPTH_BUFFER
        if(usingPackedDepthStencil)
        {
            glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
        }
        else
#    endif
#    if defined(GL_OES_stencil8) || defined(GL_ES_VERSION_2_0)
        if(Stencil8Supported && requestedStencilBits >= 8)
        {
            glGenRenderbuffersOES(1, &stencilRenderbuffer);
            glBindRenderbufferOES(GL_RENDERBUFFER_OES, stencilRenderbuffer);
            glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_STENCIL_INDEX8_OES, backingWidth, backingHeight);
            glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, stencilRenderbuffer);
        }
#    endif
#endif
		if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) 
		{
			glf::Console::Print("failed to make complete framebuffer object.");
            [self destroyFrameBuffer];
			return NO;
		}
	}
	return YES;
}


- (void) destroyFrameBuffer
{
	if(viewFramebuffer)
	{
		glDeleteFramebuffersOES(1, &viewFramebuffer);
		viewFramebuffer = 0;
	}
	if(viewRenderbuffer)
	{
		glDeleteRenderbuffersOES(1, &viewRenderbuffer);
		viewRenderbuffer = 0;
	}
	
	if(depthRenderbuffer) 
	{
		glDeleteRenderbuffersOES(1, &depthRenderbuffer);
		depthRenderbuffer = 0;
	}
    
    if(stencilRenderbuffer)
    {
        glDeleteRenderbuffersOES(1, &stencilRenderbuffer);
        stencilRenderbuffer = 0;
    }
    
#ifdef GL_APPLE_framebuffer_multisample
    if(resolveFramebuffer)
    {
        glDeleteFramebuffersOES(1, &resolveFramebuffer);
        resolveFramebuffer = 0;
    }
    
    if(resolveColorbuffer)
    {
        glDeleteRenderbuffersOES(1, &resolveColorbuffer);
        resolveColorbuffer = 0;
    }
#endif
}

- (void) bind
{
	//NSLog(@"Bind view %08X: %d\n", (int)self, viewFramebuffer);
	[EAGLContext setCurrentContext:context];
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);    
}

- (void) flush
{
	//NSLog(@"Flush view %08X: %d\n", (int)self, viewFramebuffer);
#ifdef GL_APPLE_framebuffer_multisample
    if(activateMSAA && resolveFramebuffer != 0)
    {
        /* Resolve from msaaFramebuffer to resolveFramebuffer */
        //glDisable(GL_SCISSOR_TEST);     // Ouch this line can break the driver's shadow state...
        glBindFramebufferOES(GL_READ_FRAMEBUFFER_APPLE, viewFramebuffer);
        glBindFramebufferOES(GL_DRAW_FRAMEBUFFER_APPLE, resolveFramebuffer);
        glResolveMultisampleFramebufferAPPLE();
        
        GLenum attachments[] = { GL_COLOR_ATTACHMENT0_OES, GL_DEPTH_ATTACHMENT_OES, GL_STENCIL_ATTACHMENT_OES };
        glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE, 3, attachments);
        
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, resolveColorbuffer);
        [context presentRenderbuffer:GL_RENDERBUFFER_OES];
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    }
    else 
#endif
    {
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
        [context presentRenderbuffer:GL_RENDERBUFFER_OES];
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    }
}

- (void) layoutSubviews
{
}

- (void) dealloc
{
	[self destroyFrameBuffer];
	[context release];
	
    [super dealloc];
}

- (void) initGL
{

	// Check extensions for supported stencil and depth buffers
	const char *extensions = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
#ifdef GL_OES_stencil1
	Stencil1Supported = strstr(extensions, "GL_OES_stencil1") != 0;
#endif
#ifdef GL_OES_stencil4
	Stencil4Supported = strstr(extensions, "GL_OES_stencil4") != 0;
#endif		
#ifdef GL_OES_stencil8
	Stencil8Supported = strstr(extensions, "GL_OES_stencil8") != 0;
#endif
	if (!Stencil8Supported && [context API] == kEAGLRenderingAPIOpenGLES2)
		// GLES 2.0 supports Stencil8 without extensions
		Stencil8Supported = true;
#ifdef GL_OES_packed_depth_stencil
	PackedDepth24Stencil8Supported = strstr(extensions, "GL_OES_packed_depth_stencil") != 0;
#endif
#ifdef GL_OES_depth24
	Depth24Supported = strstr(extensions, "GL_OES_depth24") != 0;
#endif
#ifdef GL_OES_depth32
	Depth32Supported = strstr(extensions, "GL_OES_depth32") != 0;
#endif

}

@end
